/*----------------------------------------------------------------------
 *
 * Copyright 2009, Thomas Dejanovic.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 *---------------------------------------------------------------------
 *
 * Low Pin Count Bus Interface module.
 *
 * This is not realy intels LPCB but was inspired by it.
 *
 *---------------------------------------------------------------------*/


module dcom_lpcb_if

  (/*AUTOARG*/
   // Outputs
   tx_data, tx_data_valid, lpcb_frame_out, lpcb_cad_out,
   // Inputs
   clk, clk_en, reset, rx_data, rx_data_valid, rx_error, tx_ready,
   lpcb_frame_in, lpcb_cad_in
   );

  //----------------------------------------
  input                 clk;
  input                 clk_en;

  input                 reset;
  //----------------------------------------

  //----------------------------------------
  // data from the host
  input [7:0]           rx_data;
  input                 rx_data_valid;
  input                 rx_error;

  // data to be sent to the host.
  output [7:0]          tx_data;
  output                tx_data_valid;
  input                 tx_ready;
  //----------------------------------------

  //----------------------------------------
  // lpcb interface
  output                lpcb_frame_out;
  output [3:0]          lpcb_cad_out;

  input                 lpcb_frame_in;
  input [3:0]           lpcb_cad_in;
  //----------------------------------------

  /*----------------------------------------------------------------*/

  /*-AUTOUNUSED-*/

  /*AUTOINPUT*/

  /*AUTOOUTPUT*/

  /*-AUTOREGINPUT-*/

  /*AUTOREG*/

  /*AUTOWIRE*/

  /*------------------------------------------------------------------
   *
   * local definitions and connections.
   *
   * */

`include "lpcb_defines.vh"

  /*-----------------------------------------------------------------
   *
   * need a small Rx fifo.
   *
   * */

  reg                   rx_fifo_pop;
  wire                  rx_fifo_data_valid, rx_fifo_data_valid_int;
  wire [7:0]            rx_fifo_data;

  reg [7:0]             rx_fifo_data_m [0:7];
  reg [2:0]             write_p, read_p;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      rx_fifo_data_m[0] <= 8'd0;
      write_p <= 3'd0;
      read_p  <= 3'd0;
    end else begin
      if ( rx_data_valid && ( (write_p != (read_p - 3'd1)) || rx_fifo_pop) ) begin
        rx_fifo_data_m[write_p] <= rx_data;
        write_p <= write_p + 3'd1;
      end

      if (rx_fifo_pop && rx_fifo_data_valid_int) begin
        read_p <= read_p + 3'd1;
      end
    end
  end

  assign rx_fifo_data = rx_fifo_data_m[read_p];
  assign rx_fifo_data_valid_int = (read_p != write_p);
  assign rx_fifo_data_valid     = rx_fifo_data_valid_int & ~rx_fifo_pop;

  /*------------------------------------------------------------------
   *
   * def __init__(self, ser, OpCodes={'read':0x21,'write':0x22},
   * byteOrder='>', format='B', opCodeLen=2, addressLen=3, dataLen=2):
   *
   * 'format' is the format character used by the struct module for
   * packing/unpacking serial data (default 1 unsigned char from int.)
   *
   * 'opCodeLen' is the opcodes' length in bytes.
   *
   * 'addressLen' is the register address' length in bytes.
   *
   * 'dataLen' is the data bus width in bytes.  All data values
   * read/written to the serial port will be of this width.
   *
   *------------------------------------------------------------------
   *
   * main fsm.
   *
   * */

  parameter [4:0] // synopsys enum rx_state
    RX_IDLE           = 4'h0,

    GET_ADDR          = 4'h1,
    GET_WRITE_DATA    = 4'h2,
    SEND_COMMAND      = 4'h3,
    SEND_ADDRESS      = 4'h7,
    SEND_DATA         = 4'h5,

    RX_PAUSE          = 4'h6,

    GET_READ_DATA     = 4'h4,

    RX_ABORT          = 4'hd,
    RX_RESET          = 4'he,

    END               = 4'hf;


reg [4:0] /* synopsys enum rx_state */ rx_state;

`ifdef IVERILOG
/*AUTOASCIIENUM("rx_state", "ascii_rx_state", nul)*/
// Beginning of automatic ASCII enum decoding
reg [111:0]             ascii_rx_state;         // Decode of rx_state
always @(rx_state) begin
  case ({rx_state})
    RX_IDLE:        ascii_rx_state = "rx_idle       ";
    GET_ADDR:       ascii_rx_state = "get_addr      ";
    GET_WRITE_DATA: ascii_rx_state = "get_write_data";
    SEND_COMMAND:   ascii_rx_state = "send_command  ";
    SEND_ADDRESS:   ascii_rx_state = "send_address  ";
    SEND_DATA:      ascii_rx_state = "send_data     ";
    RX_PAUSE:       ascii_rx_state = "rx_pause      ";
    GET_READ_DATA:  ascii_rx_state = "get_read_data ";
    RX_ABORT:       ascii_rx_state = "rx_abort      ";
    RX_RESET:       ascii_rx_state = "rx_reset      ";
  END:            ascii_rx_state = "end           ";
  default:        ascii_rx_state = "%Error        ";
endcase
end
  // End of automatics
`endif

  //
  // FSM
  //

  reg        time_out_flag;

  reg        command_done;
  reg        read_command_done;
  reg        command_done_set;
  reg        read_command_done_set;
  reg [2:0]  rx_count;
  reg [2:0]  data_count;

  reg [31:0] address;
  reg [31:0] write_data;
  reg        write_cmd;
  wire [1:0] address_range;

  assign                address_range = (|address[31:24]) ? 2'd3 :
                                        (|address[23:16]) ? 2'd2 :
                                        (|address[15: 8]) ? 2'd1 :
                                        2'd0;

  reg        lpcb_frame_out;
  reg [3:0]  lpcb_cad_out;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      rx_state    <= RX_IDLE;
      rx_count    <= 3'd3;
      data_count  <= 3'd0;
      rx_fifo_pop <= 1'd0;

      address     <= 32'd0;
      write_data  <= 32'd0;
      write_cmd   <= 1'd0;

      lpcb_frame_out <= 1'd0;
      lpcb_cad_out   <= 4'd0;

      command_done      <= 1'd0;
      read_command_done <= 1'd0;
    end else begin
      rx_fifo_pop    <= 1'd0;
      lpcb_frame_out <= 1'd0;
      lpcb_cad_out   <= 4'd0;

      command_done      <= command_done | command_done_set;
      read_command_done <= read_command_done | read_command_done_set;
      if (time_out_flag)
        rx_state  <= RX_ABORT;
      else
        casez(rx_state)
          RX_IDLE: begin
            rx_count    <= 3'd3; // used to index address bytes.
            data_count  <= 3'd0;
            write_data  <= 32'd0;
            write_cmd   <= 1'd0;
            command_done       <= 1'd0;
            read_command_done  <= 1'd0;
            // address     <= 32'd0;
            if (rx_error) begin
              // do something sane here?
              rx_state <= RX_IDLE;
            end else if (rx_fifo_data_valid) begin
              write_cmd  <= (rx_fifo_data[3:0] == `LPCB_WRITE_SUB_OPCODE) |
                            (rx_fifo_data[3:0] == `LPCB_REPEAT_WRITE_SUB_OPCODE);
              rx_fifo_pop <= 1'd1;
              case (rx_fifo_data[3:0])
                `LPCB_READ_SUB_OPCODE         : rx_state <= GET_ADDR;
                `LPCB_WRITE_SUB_OPCODE        : rx_state <= GET_ADDR;
                `LPCB_REPEAT_READ_SUB_OPCODE  : rx_state <= SEND_COMMAND;
                `LPCB_REPEAT_WRITE_SUB_OPCODE : rx_state <= GET_WRITE_DATA;
                `LPCB_ABORT_SUB_OPCODE        : rx_state <= RX_ABORT;
                `LPCB_RESET_SUB_OPCODE        : rx_state <= RX_RESET;
                default                       : rx_state <= RX_IDLE;
              endcase // case(rx_fifo_data[3:0])

              // store the number of data words in this transaction -
              // this is support for block read and write
              // transactions.
              case (rx_fifo_data[7:6])
                2'd0    : data_count  <= 3'd0;
                2'd1    : data_count  <= 3'd1;
                2'd2    : data_count  <= 3'd3;
                2'd3    : data_count  <= 3'd7;
                default : data_count  <= 3'd0;
              endcase // case(rx_fifo_data[7:6])
            end
          end

          GET_ADDR: begin
            if (rx_error) begin
              // do something sane here?
              rx_state   <= RX_IDLE;
            end else if (rx_fifo_data_valid) begin
              rx_fifo_pop <= 1'd1;
              /* -----\/----- EXCLUDED -----\/-----
               case(rx_count[1:0])
               2'd3 : address[31:24] <= rx_fifo_data;
               2'd2 : address[23:16] <= rx_fifo_data;
               2'd1 : address[15: 8] <= rx_fifo_data;
               2'd0 : address[ 7: 0] <= rx_fifo_data;
            endcase // case(rx_count)
               -----/\----- EXCLUDED -----/\----- */
              address <= {address[23:0], rx_fifo_data};
              if (rx_count == 3'd0) begin
                rx_state <= write_cmd ? GET_WRITE_DATA : SEND_COMMAND;
              end else begin
                rx_state <= GET_ADDR;
              end
              rx_count[1:0] <= rx_count[1:0] - 2'd1;
            end else
              rx_state   <= GET_ADDR;
          end

          GET_WRITE_DATA: begin
            if (rx_error) begin
              // do something sane here?
              rx_state   <= RX_IDLE;
            end else if (rx_fifo_data_valid) begin
              rx_fifo_pop <= 1'd1;
              /* -----\/----- EXCLUDED -----\/-----
               case(rx_count[1:0])
               2'd3 : write_data[31:24] <= rx_fifo_data;
               2'd2 : write_data[23:16] <= rx_fifo_data;
               2'd1 : write_data[15: 8] <= rx_fifo_data;
               2'd0 : write_data[ 7: 0] <= rx_fifo_data;
            endcase // case(rx_count)
               -----/\----- EXCLUDED -----/\----- */
              write_data <= {write_data[23:0], rx_fifo_data};
              if (rx_count == 3'd0) begin
                rx_state <= SEND_COMMAND;
              end else begin
                rx_state <= GET_WRITE_DATA;
              end
              rx_count <= rx_count - 3'd1;
            end else
              rx_state <= GET_WRITE_DATA;
          end

          SEND_COMMAND: begin
            lpcb_frame_out <= 1'd1;
            if (write_cmd) begin
              case(address_range)
                2'd0 : lpcb_cad_out <= `LPCB_WRITE_8B_CMD;
                2'd1 : lpcb_cad_out <= `LPCB_WRITE_16B_CMD;
                2'd2 : lpcb_cad_out <= `LPCB_WRITE_24B_CMD;
                2'd3 : lpcb_cad_out <= `LPCB_WRITE_32B_CMD;
              endcase // case(address_range)
            end else begin
              case(address_range)
                2'd0 : lpcb_cad_out <= `LPCB_READ_8B_CMD;
                2'd1 : lpcb_cad_out <= `LPCB_READ_16B_CMD;
                2'd2 : lpcb_cad_out <= `LPCB_READ_24B_CMD;
                2'd3 : lpcb_cad_out <= `LPCB_READ_32B_CMD;
              endcase // case(address_range)
            end
            rx_state <= SEND_ADDRESS;
            rx_count <= {address_range, 1'd1};
          end

          SEND_ADDRESS: begin
            lpcb_frame_out <= 1'd0;
            case(rx_count[2:0])
              3'd7 : lpcb_cad_out <= address[31:28];
              3'd6 : lpcb_cad_out <= address[27:24];
              3'd5 : lpcb_cad_out <= address[23:20];
              3'd4 : lpcb_cad_out <= address[19:16];
              3'd3 : lpcb_cad_out <= address[15:12];
              3'd2 : lpcb_cad_out <= address[11: 8];
              3'd1 : lpcb_cad_out <= address[ 7: 4];
              3'd0 : lpcb_cad_out <= address[ 3: 0];
              default : lpcb_cad_out <= 4'd0;
            endcase // case(rx_count)
            /* -----\/----- EXCLUDED -----\/-----
             // need to preserve address in FSM loop
             lpcb_cad_out <= address[31:28];
             address <= {address[27:0], 4'd0};
             -----/\----- EXCLUDED -----/\----- */
            if (rx_count == 3'd0) begin
              rx_count <= 3'd7;
              if (write_cmd)
                rx_state <= SEND_DATA;
              else
                rx_state <= GET_READ_DATA;
            end else begin
              rx_count <= rx_count - 3'd1;
              rx_state <= SEND_ADDRESS;
            end
          end

          SEND_DATA: begin
            lpcb_frame_out <= 1'd0;
            /* -----\/----- EXCLUDED -----\/-----
             case(rx_count[2:0])
             3'd7 : lpcb_cad_out <= write_data[31:28];
             3'd6 : lpcb_cad_out <= write_data[27:24];
             3'd5 : lpcb_cad_out <= write_data[23:20];
             3'd4 : lpcb_cad_out <= write_data[19:16];
             3'd3 : lpcb_cad_out <= write_data[15:12];
             3'd2 : lpcb_cad_out <= write_data[11: 8];
             3'd1 : lpcb_cad_out <= write_data[ 7: 4];
             3'd0 : lpcb_cad_out <= write_data[ 3: 0];
             default : lpcb_cad_out <= 4'd0;
          endcase // case(rx_count)
             -----/\----- EXCLUDED -----/\----- */
            lpcb_cad_out <= write_data[31:28];
            write_data <= {write_data[27:0], 4'd0};
            if (rx_count == 3'd0) begin
              if (data_count == 3'd0) begin
                rx_state <= RX_IDLE;
              end else begin
                rx_state <= RX_PAUSE;
              end
            end else begin
              rx_state <= SEND_DATA;
            end
            rx_count <= rx_count - 3'd1;
          end

          RX_PAUSE: begin
            rx_count    <= 3'd3;
            if (command_done) begin
              rx_state   <= GET_WRITE_DATA;
              data_count <= data_count - 3'd1;
              command_done      <= 1'd0;
              read_command_done <= 1'd0;
            end else begin
              rx_state   <= RX_PAUSE;
            end
          end

          GET_READ_DATA: begin
            // wait here until the current read command has been ack'd
            // and the data put into the read fifo.
            if (read_command_done) begin
              if (data_count == 3'd0) begin
                rx_state <= RX_IDLE;
              end else begin
                rx_state   <= SEND_COMMAND;
              end
              data_count <= data_count - 3'd1;
              command_done      <= 1'd0;
              read_command_done <= 1'd0;
            end else begin
              rx_state <= GET_READ_DATA;
            end
          end

          RX_ABORT: begin
            lpcb_frame_out <= 1'd1;
            lpcb_cad_out   <= `LPCB_ABORT_CMD;
            rx_state <= RX_IDLE;
          end

          RX_RESET: begin
            lpcb_frame_out <= 1'd1;
            lpcb_cad_out   <= `LPCB_RESET_CMD;
            rx_state <= RX_IDLE;
          end

          default : begin
            rx_state <= RX_IDLE;
          end
        endcase // casez(rx_state)
    end
  end

  /*------------------------------------------------------------------
   *
   * monitor the return interface and stuff any recieved data into the tx fifo.
   *
   * */

  parameter [1:0] // synopsys enum tx_state
    TX_IDLE           = 2'd0,
    GET_DATA          = 2'd1,
    TX_DATA           = 2'd2,
    TX_END            = 2'd3;

  reg [1:0]       /* synopsys enum tx_state */ tx_state;

`ifdef IVERILOG
  /*AUTOASCIIENUM("tx_state", "ascii_tx_state", nul)*/
  // Beginning of automatic ASCII enum decoding
  reg [63:0]      ascii_tx_state;         // Decode of tx_state
  always @(tx_state) begin
    case ({tx_state})
      TX_IDLE:  ascii_tx_state = "tx_idle ";
      GET_DATA: ascii_tx_state = "get_data";
      TX_DATA:  ascii_tx_state = "tx_data ";
      TX_END:   ascii_tx_state = "tx_end  ";
      default:  ascii_tx_state = "%Error  ";
    endcase
  end
  // End of automatics
`endif


  reg [2:0]             tx_count;
  reg [31:0]            read_data;

  reg [7:0]             tx_data;
  reg                   tx_data_valid;
  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      tx_state      <= TX_IDLE;
      tx_count      <= 3'd7;
      read_data     <= 32'd0;
      tx_data       <= 8'd0;
      tx_data_valid <= 1'd0;
      command_done_set      <= 1'd0;
      read_command_done_set <= 1'd0;
    end else begin
      tx_data_valid <= 1'd0;
      command_done_set <= 1'd0;
      read_command_done_set <= 1'd0;
      if (time_out_flag)
        tx_state  <= TX_IDLE;
      else
        case(tx_state)
          TX_IDLE: begin
            tx_count  <= 3'd7;
            if (lpcb_frame_in) begin
              if (lpcb_cad_in == `LPCB_READ_ACK_CMD) begin
                tx_state  <= GET_DATA;
              end else begin
                tx_state  <= TX_IDLE;
                // this was some other command that has closed the loop.
                command_done_set  <= 1'd1;
              end
            end else
              tx_state  <= TX_IDLE;
          end

          GET_DATA: begin
            if (lpcb_frame_in) begin
              tx_count  <= 3'd7;
              if (lpcb_cad_in == `LPCB_READ_ACK_CMD) begin
                // abort this read and collect the new one.  This is
                // technically an error but try to be gracefull.
                tx_state  <= GET_DATA;
              end else begin
                // this is the abort or reset comand
                tx_state  <= TX_IDLE;
              end
            end else begin
              /* -----\/----- EXCLUDED -----\/-----
               case(tx_count[2:0])
               3'd7 : read_data[31:28] <= lpcb_cad_in;
               3'd6 : read_data[27:24] <= lpcb_cad_in;
               3'd5 : read_data[23:20] <= lpcb_cad_in;
               3'd4 : read_data[19:16] <= lpcb_cad_in;
               3'd3 : read_data[15:12] <= lpcb_cad_in;
               3'd2 : read_data[11: 8] <= lpcb_cad_in;
               3'd1 : read_data[ 7: 4] <= lpcb_cad_in;
               3'd0 : read_data[ 3: 0] <= lpcb_cad_in;
            endcase // case(count)
               -----/\----- EXCLUDED -----/\----- */
              read_data <= {read_data[27: 0], lpcb_cad_in};
              if (tx_count == 3'd0) begin
                tx_count <= 3'd3;
                tx_state <= TX_DATA;
              end else begin
                tx_count <= tx_count - 3'd1;
                tx_state <= GET_DATA;
              end
            end
          end

          TX_DATA: begin
            // ignoring any incoming commands at this point.
            if (tx_ready & ~tx_data_valid) begin
              tx_data_valid <= 1'd1;
              case(tx_count[1:0])
                2'd3 : tx_data <= read_data[31:24];
                2'd2 : tx_data <= read_data[23:16];
                2'd1 : tx_data <= read_data[15: 8];
                2'd0 : tx_data <= read_data[ 7: 0];
              endcase // case(count)
              /* -----\/----- EXCLUDED -----\/-----
               tx_data          <= read_data[31:24];
               //            read_data[31: 8] <= read_data[23: 0];
               read_data <= {read_data[23: 0], 8'h0};
               -----/\----- EXCLUDED -----/\----- */
            end else if (tx_ready & tx_data_valid) begin
              //2bit wide??            tx_data_valid <= 1'd1;
              if (tx_count == 3'd0) begin
                tx_state <= TX_IDLE;
                read_command_done_set <= 1'd1;
              end else begin
                tx_state <= TX_DATA;
              end
              tx_count <= tx_count - 3'd1;
            end else begin
              tx_state <= TX_DATA;
            end
          end

          default : tx_state <= TX_IDLE;

        endcase // case(tx_state)
    end
  end

  /*------------------------------------------------------------------
   *
   * Time out on message.
   *
   * */

  parameter TIME_OUT_TIME = 20'h80000;
  parameter TIME_OUT_ONE  = 20'h1;

  reg [19:0] time_out_count;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      time_out_count <= TIME_OUT_TIME;
    end else begin
      if (rx_state != RX_IDLE)
        time_out_count <= time_out_count - TIME_OUT_ONE;
      else
        time_out_count <= TIME_OUT_TIME;
    end
  end

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      time_out_flag   <= 1'd0;
    end else begin
      if (|time_out_count)
        time_out_flag   <= 1'd0;
      else
        time_out_flag   <= 1'd1;
    end
  end

  /*----------------------------------------------------------------*/

endmodule // dcom_lpcb_if


// Local Variables:
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:
